/*
 * Copyright 2012 Christian Schindelhauer, Peter Thiemann, Faisal Aslam, Luminous Fennell and Gidon Ernst.
 * All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * only, as published by the Free Software Foundation.
 * 
 * This code is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 3 for more details (a copy is
 * included in the LICENSE file that accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License
 * version 3 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Faisal Aslam 
 * (faisal.aslam AT gmail.com)
 * if you need additional information or have any questions.
 */
package takatuka.classreader.dataObjs.attribute;

import org.apache.commons.lang.builder.*;
import takatuka.classreader.dataObjs.*;
import takatuka.classreader.logic.file.*;
import takatuka.io.*;
import java.util.*;
import takatuka.classreader.logic.factory.FactoryFacade;
import takatuka.classreader.logic.factory.FactoryPlaceholder;
import takatuka.classreader.logic.util.*;

/**
 * <p>Title: </p>
 * <p>Description: </p>
 * @author Faisal Aslam
 * @version 1.0
 */
public class Instruction implements BaseObject {

    private static long instructionCount = 1;
    protected long instructionId = 0;
    protected String cacheMne = null;
    private int opcode = -1;
    //Vector operands = new Vector();
    private Un operands = null;
    private static final String OPNEM = "opcode-mnemonic.properties";
    protected int offSet = 0;
    private static final Properties opcodeMnemoicProperties = PropertyReader.getInstanceOf().loadProperties(OPNEM);
    private static HashMap mnemonicOpcodeMap = new HashMap();
    protected static FactoryFacade factory = FactoryPlaceholder.getInstanceOf().getFactory();
    private Un originalOperand = null;
    private static TreeMap delMe = new TreeMap();
    private int originalOffSet = -1;
    private CodeAtt parentCodeAtt = null;

    public Instruction() {
        if (this.instructionId == 0) {
            this.instructionId = instructionCount++;
        }
    }

    /**
     *
     * @return the unique Id per instruction. It is very important
     */
    public long getInstructionId() {
        return instructionId;
    }

    /**
     *
     * @return the line number of the instruction.
     */
    public int getLineNumber() {
        return 0; //LineNumberController.getInstanceOf().getLineNumberInfo(instructionId);
    }

    public Instruction(int opcode, Un operands, CodeAtt parentCodeAtt) {
        this();
        this.opcode = opcode;
        try {
            if (operands == null) {
                operands = factory.createUn();
            }
        } catch (Exception d) {
            d.printStackTrace();
            Miscellaneous.exit();
        }
        this.operands = operands;
        this.originalOperand = (Un) operands.clone();
        this.parentCodeAtt = parentCodeAtt;
    }

    public CodeAtt getParentCodeAtt() {
        return parentCodeAtt;
    }

    public void setParentCodeAtt(CodeAtt parentCodeAtt) {
        this.parentCodeAtt = parentCodeAtt;
    }

    public MethodInfo getMethod() {
        return getParentCodeAtt().getMethod();
    }

    public void setOriginalOffSet(int offSet) {
        if (originalOffSet != -1) {
            Miscellaneous.printlnErr("Cannot set offset more than once");
            Miscellaneous.exit();
        }
        this.originalOffSet = offSet;
    }

    public int getOriginalOffSet() {
        return this.originalOffSet;
    }

    public static void printNotUsed_delMe() {
        for (int loop = 0; loop < 256; loop++) {
            if (!delMe.containsKey(loop)
                    && opcodeMnemoicProperties.getProperty(loop + "") != null) {
                Miscellaneous.println(loop + ", " + opcodeMnemoicProperties.getProperty(loop + ""));
            }
        }
    }

    public Un getOriginalOperand() {
        return originalOperand;
    }

    /**
     * 
     * @return true if the instruction referes to a constant pool
     */
    public boolean isCPInstruction() {
        if (getMnemonic().startsWith("ANEWARRAY")
                || getMnemonic().startsWith("CHECKCAST")
                || getMnemonic().startsWith("GETFIELD")
                || getMnemonic().startsWith("GETSTATIC")
                || getMnemonic().startsWith("INSTANCEOF")
                || getMnemonic().startsWith("INVOKE")
                || getMnemonic().startsWith("LDC")
                || getMnemonic().startsWith("MULTIANEWARRAY")
                || getMnemonic().equals("NEW")
                || getMnemonic().startsWith("PUTFIELD")
                || getMnemonic().startsWith("PUTSTATIC")) {
            return true;
        }
        return false;
    }

    /**
     * It return ture or false based on the Mnmenonic of the instruction. For example
     * for instructions like ifeq, it returns true
     * @return true if this instrucion is branch source
     */
    public boolean isBranchSourceInstruction() {
        if (getMnemonic().startsWith("GOTO")
                || getMnemonic().startsWith("IF")
                || getMnemonic().startsWith("JSR")
                || getMnemonic().startsWith("LOOKUPSWITCH")
                || getMnemonic().startsWith("TABLESWITCH")) {
            return true;
        }
        return false;
    }

    @Override
    public Object clone() {
        Instruction ret = null;
        try {
            ret = factory.createInstruction(opcode, (Un) operands.clone(), parentCodeAtt);
        } catch (Exception d) {
            d.printStackTrace();
            Miscellaneous.exit();
        }
        return ret;
    }

    public int getMaxStack1() {
        return 0;
    }

    public int getMaxLocals1() {
        return 0;
    }

    public Un getOperandsData() {
        return operands;
    }

    public void setOffSet(int address) {
        this.offSet = address;
    }

    public int getOffSet() {
        return offSet;
    }

    /**
     * returns the length of instruction. That is number of bytes used in all operands + 1(for opcode).
     * @return int
     */
    public int length() {
        return operands.size() + 1;
    }

    public int getOpCode() {
        return opcode;
    }

    /**
     * Also set the Mnemonic. Otherwise, the instruction will have old cached Mnemonic.
     * @param opcode
     */
    public void setOpCode(int opcode) {
        this.opcode = opcode;
        //cacheMne = null; //Mnemonic is changed too...
    }

    public void setOperandsData(Un data) {
        this.operands = data;
    }

    public void setMnemonic(String mn) {
        this.cacheMne = mn;
    }

    public static int getOpcode(String mneomonic) {
        if (mnemonicOpcodeMap == null || mnemonicOpcodeMap.size() == 0) {
            //following are opcodes
            Enumeration enm = opcodeMnemoicProperties.propertyNames();
            while (enm.hasMoreElements()) {
                String name = (String) enm.nextElement();
                //we have {mnemoic, opcode} map constructed here
                mnemonicOpcodeMap.put(opcodeMnemoicProperties.getProperty(name).
                        trim().toUpperCase(), Integer.parseInt(name));
            }
        }
        Integer retOPCODE = (Integer) mnemonicOpcodeMap.get(mneomonic);
        if (retOPCODE == null) {
            return -1;
        }
        return retOPCODE;
    }

    public static void setAllOffsets(Vector<Instruction> allInstrInput) {
        int offset = 0;
        for (int loop = 0; loop < allInstrInput.size(); loop++) {
            Instruction currInstr = allInstrInput.elementAt(loop);
            currInstr.setOffSet(offset);
            offset += currInstr.length();
        }

    }

    public static String getMnemonic(int opcode) {
        try {
            return opcodeMnemoicProperties.getProperty(opcode + "").trim().
                    toUpperCase();
        } catch (NullPointerException ex) {
            Miscellaneous.printlnErr("cannot find mnemonic for opcode =" + opcode);
            ex.printStackTrace();
            Miscellaneous.exit();
        }
        return null;
    }

    public String getMnemonic() {
        if (cacheMne == null) {
            this.cacheMne = opcodeMnemoicProperties.getProperty(opcode + "");
            if (cacheMne != null) {
                cacheMne = cacheMne.trim().toUpperCase();
            } else {
                Miscellaneous.printlnErr("cannot get the mnemonic for opcode = " + opcode);
                Miscellaneous.exit();
            }
        }
        return cacheMne;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Instruction)) {
            return false;
        }

        Instruction inst = (Instruction) obj;
        if (opcode == inst.opcode && operands.equals(inst.operands)
                && getMnemonic().equals(inst.getMnemonic())) {
            return true;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return (new HashCodeBuilder(17, 37)).append(opcode).append(operands).
                toHashCode();
    }

    @Override
    public String toString() {
        String ret = "";
        try {
            ret = writeSelected(null);
        } catch (Exception d) {
            d.printStackTrace();
            Miscellaneous.exit();
        }
        return ret;
    }

    @Override
    public String writeSelected(BufferedByteCountingOutputStream buff) throws
            Exception {
        String ret = "";
        ret = ret + "0x" + Integer.toHexString(getOffSet()) + ":"
                + getInstructionId() + "   " + getMnemonic();
        new Un(getOpCode()).trim(1).writeSelected(buff);
        ret = ret + " " + (getOperandsData().size() > 0 ? "0x" : "")
                + getOperandsData().writeSelected(buff);
        return ret;
    }
}
